﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace ElasticReflection.Emit
{
    public class EmitType
    {
        TypeBuilder TB;
        EmitAssembly Parent;

        internal EmitType(TypeBuilder TB, EmitAssembly Parent)
        {
            this.TB = TB;
            this.Parent = Parent;
        }


        public EmitMethod<TDelegate> DefineMethod<TDelegate>(string Name, Expression<TDelegate> Body)
        {
            return new EmitMethod<TDelegate>(Name,TB, this, Body);
        }


        public EmitType Inherits(Type Base)
        {
            TB.SetParent(Base);
            return this;
        }
        public EmitType Implements(Type Interface)
        {
            TB.AddInterfaceImplementation(Interface);
            return this;
        }

        public EmitType Generic(string Name, Type BaseConstraint, params Type[] InterfaceConstrains)
        {
            var Gs = TB.DefineGenericParameters(Name);
            if (BaseConstraint != null) Gs[0].SetBaseTypeConstraint(BaseConstraint);
            if (InterfaceConstrains != null && InterfaceConstrains.Count(t => t != null) > 0)
                Gs[0].SetInterfaceConstraints(InterfaceConstrains.Where(t => t != null).ToArray());
            return this;
        }


        public EmitType DefineProperty<T>(string Name, Expression<Func<T>> Get, Expression<Func<T, object>> Set)
        {
            var PB = TB.DefineProperty(Name, PropertyAttributes.None, typeof(T), new Type[0]);
            if (Get != null)
            {
                MethodBuilder GetBuilder = TB.DefineMethod("get_" + Name, MethodAttributes.Public | MethodAttributes.Static, typeof(T), new Type[0]);
                Get.CompileToMethod(GetBuilder);
                PB.SetGetMethod(GetBuilder);
            }

            if (Set != null)
            {
                MethodBuilder SetBuilder = TB.DefineMethod("set_" + Name, MethodAttributes.Public | MethodAttributes.Static, null, new Type[] { typeof(T) });
                Set.CompileToMethod(SetBuilder);
                PB.SetSetMethod(SetBuilder);
            }

            return this;
        }

        public EmitAssembly End()
        {
            TB.CreateType();
            return Parent;
        }



    }

    public class EmitMethod<TDelegate>
    {
        TypeBuilder TB;
        EmitType Parent;
        Expression<TDelegate> Body;
        string Name;

        Type ReturnType;
        List<Type> Parameters = new List<Type>();


        public EmitMethod(string Name,TypeBuilder TB, EmitType Parent, Expression<TDelegate> Body)
        {
            this.TB = TB;
            this.Parent = Parent;
            this.Body = Body;
            this.Name = Name;
        }
        
        public EmitMethod<TDelegate> Return(Type Type)
        {
            ReturnType = Type;
            return this;
        }
        
        public EmitMethod<TDelegate> Parameter(Type Type)
        {
            Parameters.Add(Type);
            return this;
        }


        //public EmitMethod<TDelegate> Out(string Name, Type Type)
        //{
        //    MB.DefineParameter(pInex, ParameterAttributes.Out, Name);
        //    pInex++;
        //    return this;
        //}

        //public EmitMethod<TDelegate> Generic(string Name, Type BaseConstraint, params Type[] InterfaceConstrains)
        //{
        //    var Gs = MB.DefineGenericParameters(Name);
        //    if (BaseConstraint != null) Gs[0].SetBaseTypeConstraint(BaseConstraint);
        //    if (InterfaceConstrains != null && InterfaceConstrains.Count(t => t != null) > 0)
        //        Gs[0].SetInterfaceConstraints(InterfaceConstrains.Where(t => t != null).ToArray());
        //    return this;
        //}


        public EmitType End()
        {
            MethodBuilder MB = TB.DefineMethod(Name, MethodAttributes.Public | MethodAttributes.Static,ReturnType,Parameters.ToArray());
            Body.CompileToMethod(MB);
            return Parent;
        }


    }
}
